package com.flow.framework.module.call.rpc.manager;

import com.flow.framework.common.util.verify.VerifyUtil;
import com.flow.framework.core.system.listener.lifecycle.ISystemLifecycleListener;
import com.flow.framework.module.call.properties.FrameworkModuleCallConfigProperties;
import com.flow.framework.module.call.rpc.properties.FrameworkRpcConfigProperties;
import com.flow.framework.module.call.rpc.util.FeignUtil;
import feign.Target;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.core.env.Environment;

import java.lang.reflect.Field;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * feign客户端的管理器
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2022/2/19
 */
@Slf4j
@RequiredArgsConstructor
public class FeignManager implements ISystemLifecycleListener {

    private static final Map<FeignClient, Target> FEIGN_CLIENT_AND_TARGET_PAIR_MAP = new ConcurrentHashMap<>();

    private static final Map<Target, Boolean> TARGET_AND_INNER_TAG_MAP = new ConcurrentHashMap<>();

    private final Environment environment;

    private final FrameworkModuleCallConfigProperties frameworkModuleCallConfigProperties;

    /**
     * 判断feign请求是否内部请求
     *
     * @param target 请求目标
     * @return 是否内部请求
     */
    public boolean isInnerRequest(Target target) {
        return isInnerRequestLoadBalance(target) || isInnerRequestHost(target);
    }

    private boolean isInnerRequestLoadBalance(Target target) {
        Boolean inner = TARGET_AND_INNER_TAG_MAP.get(target);
        return VerifyUtil.isTrue(inner);
    }

    private boolean isInnerRequestHost(Target target) {
        FrameworkRpcConfigProperties frameworkRpcConfigProperties = frameworkModuleCallConfigProperties.getRpc();
        if (null == frameworkRpcConfigProperties){
            return false;
        }
        List<String> innerRequestHosts = frameworkRpcConfigProperties.getInnerRequestHosts();
        if (VerifyUtil.isEmpty(innerRequestHosts)) {
            return false;
        }
        String urlStr = target.url();
        if (VerifyUtil.isEmpty(urlStr)) {
            return false;
        }
        try {
            URL url = new URL(urlStr);
            return innerRequestHosts.contains(url.getHost());
        } catch (Exception ignore) {
        }
        return false;
    }

    /**
     * 注册feign客户端
     *
     * @param target      请求目标
     * @param feignClient feign客户端注解
     */
    public void registerFeignClient(Target target, FeignClient feignClient) {
        // feign在初始化时，如果url不为空，则将rest调用初始化成普通调用，否则初始化成负载均衡调用；该模式为固定模式，后续无法更改，
        // 即使强行修改target的值也不会生效，但如果不修改调用类型，值修改调用地址是可以生效的
        String url = feignClient.url();
        if (VerifyUtil.isEmpty(environment.resolvePlaceholders(url))) {
            TARGET_AND_INNER_TAG_MAP.put(target, true);
        } else {
            TARGET_AND_INNER_TAG_MAP.put(target, false);
        }

        // 如果是需要动态刷新的，则缓存feign客户端注解和请求目标
        boolean isDynamicRefresh = FeignUtil.isDollarPlaceHolder(url)
                || FeignUtil.isDollarPlaceHolder(feignClient.name())
                || FeignUtil.isDollarPlaceHolder(feignClient.path());
        if (isDynamicRefresh) {
            FEIGN_CLIENT_AND_TARGET_PAIR_MAP.put(feignClient, target);
        }
    }

    @Override
    public void onRefresh() {
        Set<Map.Entry<FeignClient, Target>> entries = FEIGN_CLIENT_AND_TARGET_PAIR_MAP.entrySet();
        for (Map.Entry<FeignClient, Target> entry : entries) {
            FeignClient feignClient = entry.getKey();
            Target target = entry.getValue();

            String targetUrl;
            String url = feignClient.url();
            if (FeignUtil.isDollarPlaceHolder(url)) {
                targetUrl = environment.resolvePlaceholders(url);
            } else {
                targetUrl = url;
            }

            String name = feignClient.name();
            if (FeignUtil.isDollarPlaceHolder(name)) {
                String targetName = environment.resolvePlaceholders(name);
                if (VerifyUtil.isEmpty(targetName)) {
                    log.error("feign name is empty. context id : {}", feignClient.contextId());
                    return;
                }
                setFieldValue(target, "name", targetName);
                if (VerifyUtil.isEmpty(targetUrl)) {
                    targetUrl = "http://" + targetName;
                }
            }

            String targetPath;
            String path = feignClient.path();
            if (FeignUtil.isDollarPlaceHolder(path)) {
                targetPath = environment.resolvePlaceholders(path);
            } else {
                targetPath = path;
            }
            if (targetPath.startsWith("/")) {
                if (!targetUrl.endsWith("/")) {
                    targetUrl = targetUrl + targetPath;
                } else {
                    targetUrl = targetUrl + targetPath.substring(1);
                }
            } else {
                if (!targetUrl.endsWith("/")) {
                    targetUrl = targetUrl + "/" + targetPath;
                } else {
                    targetUrl = targetUrl + targetPath;
                }
            }
            setFieldValue(target, "url", targetUrl);
        }
    }

    private void setFieldValue(Target target, String fieldName, Object value) {
        try {
            Field field = target.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(target, value);
        } catch (Exception e) {
            log.error("set field value error.", e);
        }
    }
}
